package zm.irc.connpool;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import zm.irc.RunIrcClientBackEnd;
import zm.irc.cfg.DbConfig;
import zm.irc.ioc.annotation.AutoWired;
import zm.irc.ioc.annotation.Service;
import zm.irc.ioc.beanActoin.AfterContexinItialize;

@Service(isSinglet = true)
public class DbConnectionPool implements AfterContexinItialize{
    private static final Logger log = Logger.getLogger(DbConnectionPool.class);


    private static final ConcurrentLinkedQueue<Connection> freeConn = new ConcurrentLinkedQueue<>();
    private static final ConcurrentHashMap<Integer,Connection> inUsedConn = new ConcurrentHashMap<>();

    @AutoWired
    private DbConfig dbConfig;

    public void doAction(){
        log.info("DB CONN POOL Init...");
        if(  StringUtils.isEmpty(RunIrcClientBackEnd.dbPwd)){
            log.info("No DB PASSWORD Found. Skip.");
            return;
        }
        if(this.dbConfig.getPoolSize() > 0 ){
            this.init(dbConfig.getPoolSize());

            log.info("DB CONN Self Test...");
            close(this.getConnection());
        }
    }

    private void init(int poolSize) {
        log.info("INIT : DB Conn pool. Size is :" + poolSize);
        for(int i = 0; i< poolSize;i++){
            log.info("INIT : DB Conn pool. Index " + (i+1));
            log.error("For the password. should be refactor");
            freeConn.add(getConnection(dbConfig.getUserName(), RunIrcClientBackEnd.dbPwd));
        }
        regShutdownHock();
        log.info("INIT finished");
    }

    private void regShutdownHock(){
        Runtime.getRuntime().addShutdownHook(new Thread(()->{
            log.info("Shutting down!");
            shutdown();
        }));
    }

    private void shutdown(){
        if(CollectionUtils.isNotEmpty(freeConn)){
            freeConn.forEach(con-> {
                try {
                    con.close();
                } catch (SQLException e) {
                    log.error("close free conn error",e);
                }
            });
        }
       if( !inUsedConn.isEmpty()){
           inUsedConn.forEach((key,value)-> {
               try {
                   value.close();
               } catch (SQLException e) {
                log.error("close inUsed conn error",e);
               }
           });
       }
    }
    private Connection getConnection(String user,String pwd){
        try {
            Class.forName(dbConfig.getDriverName()).getDeclaredConstructor().newInstance();
            return DriverManager.getConnection(dbConfig.getUrl(), user, pwd);
        }catch (Exception e){
            log.error("Get connection error !",e);
        }
        return null;
    }

    public Connection getConnection(){
        if(this.dbConfig.getPoolSize() == 0){
            return null;
        }
        try {
            Connection conn = freeConn.poll();
            if (isALive(conn)) {
                inUsedConn.put(conn.hashCode(), conn);
                return conn;
            } else {
                closeConnDirect(conn);
                freeConn.add(getConnection(dbConfig.getUserName(), RunIrcClientBackEnd.dbPwd));
                return getConnection();
            }
        } catch (Exception e) {
            log.error("Get connection error !",e);
        }
        return null;

    }

    private static void closeConnDirect(Connection conn){
        try {
            if (conn != null && !conn.isClosed()) {
                conn.close();
            }
        }catch(Exception e){
            log.debug("Connection closed!");
        }
    }

    public static void close(Connection conn){
        if(conn == null){
            return;
        }

        try {
            if(!conn.getAutoCommit()){
                conn.commit();
            }

            inUsedConn.remove(conn.hashCode());
            freeConn.add(conn);
        } catch (SQLException e) {
            log.debug("close Connection error !",e);
        }

    }

    public static void close(Statement stat){
        if(stat == null){
            return;
        }

        try {
            stat.close();
        } catch (SQLException e) {
            log.debug("close Statement error !",e);
        }
    }


    public static void close(ResultSet rs){
        if(rs == null){
            return;
        }

        try {
            rs.close();
        } catch (SQLException e) {
            log.debug("close ResultSet error !",e);
        }
    }

    /**
     * <pre>
     * Check the connection is valid or not using the sql "select 1"l
     * Any Exception throws out , the connection will be marked as invalid;
     * </pre>
     * @param conn
     * @return
     */
    private static boolean isALive(Connection conn){
        try {
            if (conn == null || conn.isClosed()) {
                return false;
            }
            String sqlForCheck = "select 1";
            Statement st = conn.createStatement();
            st.execute(sqlForCheck);
            close(st);
            return true;
        }catch (Exception e){
            return false;
        }
    }

}
